package com.wbvideo.resample;

import android.media.MediaCodec;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.os.Build;
import android.support.annotation.RequiresApi;

import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.util.concurrent.LinkedBlockingQueue;

@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
public class HardMusicDecoder {
    private static final String TAG = "HardMusicDecoder";
    private static final int BUFFER_SIZE = 2048;


    public final LinkedBlockingQueue<PCM> mChunkPCMDataQueue = new LinkedBlockingQueue<>();
    private MediaExtractor mediaExtractor;
    private MediaCodec mediaDecode;
    private ByteBuffer[] decodeInputBuffers;
    private ByteBuffer[] decodeOutputBuffers;
    private MediaCodec.BufferInfo decodeBufferInfo;
    private int mSampleRate;
    private int mReSampleRate;

    boolean isPCMExtractorEOS() {
        return sawOutputEOS;
    }

    private boolean sawOutputEOS = false;

    private String mp3FilePath;

    private MediaFormat mMediaFormat;

    FileOutputStream mFileOutputStream;

    public HardMusicDecoder(String path, int reSampleRate) {
        mp3FilePath = path;
        mReSampleRate = reSampleRate;
    }

    public HardMusicDecoder startPcmExtractor() {
        initMediaDecode();

        new Thread(new Runnable() {
            @Override
            public void run() {
                srcAudioFormatToPCM();
            }
        }).start();
        return this;
    }

    public HardMusicDecoder release() {
        sawOutputEOS = true;
        mChunkPCMDataQueue.clear();
        AudioReSampleHelp.getInstance().endResample();
        return this;
    }

    public void getPCMData(final IDecoderProcessListener decoderProcessListener) {
        synchronized (TAG) {//记得加锁
            if (mChunkPCMDataQueue.isEmpty()) {
                if (null != decoderProcessListener) {
                    decoderProcessListener.onProcess(null);
                }
                return;
            }

            final byte[] pcmChunk = mChunkPCMDataQueue.poll().bufferBytes;
            if (mSampleRate == mReSampleRate) {
                if (null != decoderProcessListener) {
                    decoderProcessListener.onProcess(pcmChunk);
                }
            } else {
                AudioReSampleHelp.getInstance().processAudioData(pcmChunk, new IReSampleProcessListener() {

                    @Override
                    public void onProcess(byte[] bytes) {
                        if (null != decoderProcessListener) {
                            decoderProcessListener.onProcess(bytes);
                        }
                    }
                });
            }
        }
    }

    public MediaFormat getMediaFormat() {
        return mMediaFormat;
    }

    private void initMediaDecode() {
        try {
            mediaExtractor = new MediaExtractor();//此类可分离视频文件的音轨和视频轨道
            mediaExtractor.setDataSource(mp3FilePath);//媒体文件的位置
            for (int i = 0; i < mediaExtractor.getTrackCount(); i++) {//遍历媒体轨道 此处我们传入的是音频文件，所以也就只有一条轨道
                mMediaFormat = mediaExtractor.getTrackFormat(i);
                String mime = mMediaFormat.getString(MediaFormat.KEY_MIME);
                if (mime.startsWith("audio/")) {//获取音频轨道
                    mSampleRate = mMediaFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE);
                    mediaExtractor.selectTrack(i);//选择此音频轨道
                    mediaDecode = MediaCodec.createDecoderByType(mime);//创建Decode解码器
                    mediaDecode.configure(mMediaFormat, null, null, 0);

                    AudioReSampleHelp.getInstance().initReSampler(2, mSampleRate, 2, mReSampleRate);
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        if (mediaDecode == null) {
            return;
        }
        mediaDecode.start();//启动MediaCodec ，等待传入数据
        decodeInputBuffers = mediaDecode.getInputBuffers();//MediaCodec在此ByteBuffer[]中获取输入数据
        decodeOutputBuffers = mediaDecode.getOutputBuffers();//MediaCodec将解码后的数据放到此ByteBuffer[]中 我们可以直接在这里面得到PCM数据
        decodeBufferInfo = new MediaCodec.BufferInfo();//用于描述解码得到的byte[]数据的相关信息
    }

    private void putPCMData(byte[] pcmChunk, int bufferSize) {
        synchronized (TAG) {//记得加锁
            mChunkPCMDataQueue.offer(new PCM(pcmChunk, bufferSize));
        }
    }

    /**
     * 解码音频文件 得到PCM数据块
     */
    private void srcAudioFormatToPCM() {

        sawOutputEOS = false;
        boolean sawInputEOS = false;
        try {
            while (!sawOutputEOS) {
                if (mChunkPCMDataQueue.size() < 1000) {
                    if (!sawInputEOS) {
                        int inputIndex = mediaDecode.dequeueInputBuffer(-1);//获取可用的inputBuffer -1代表一直等待，0表示不等待 建议-1,避免丢帧
                        if (inputIndex >= 0) {
                            ByteBuffer inputBuffer = decodeInputBuffers[inputIndex];//拿到inputBuffer
                            inputBuffer.clear();//清空之前传入inputBuffer内的数据
                            int sampleSize = mediaExtractor.readSampleData(inputBuffer, 0);//MediaExtractor读取数据到inputBuffer中
                            if (sampleSize < 0) {//小于0 代表所有数据已读取完成
                                sawInputEOS = true;
                                mediaDecode.queueInputBuffer(inputIndex, 0, 0, 0L, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                            } else {
                                long presentationTimeUs = mediaExtractor.getSampleTime();
                                mediaDecode.queueInputBuffer(inputIndex, 0, sampleSize, presentationTimeUs, 0);//通知MediaDecode解码刚刚传入的数据
                                mediaExtractor.advance();//MediaExtractor移动到下一取样处
                            }
                        }
                    }

                    //获取解码得到的byte[]数据 参数BufferInfo上面已介绍 10000同样为等待时间 同上-1代表一直等待，0代表不等待。此处单位为微秒
                    //此处建议不要填-1 有些时候并没有数据输出，那么他就会一直卡在这 等待
                    int outputIndex = mediaDecode.dequeueOutputBuffer(decodeBufferInfo, 10000);
                    if (outputIndex >= 0) {
                        // Simply ignore codec config buffers.
                        if ((decodeBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
                            mediaDecode.releaseOutputBuffer(outputIndex, false);
                            continue;
                        }

                        if (decodeBufferInfo.size != 0) {

                            ByteBuffer outBuf = decodeOutputBuffers[outputIndex];//拿到用于存放PCM数据的Buffer

                            outBuf.position(decodeBufferInfo.offset);
                            outBuf.limit(decodeBufferInfo.offset + decodeBufferInfo.size);
                            byte[] data = new byte[decodeBufferInfo.size];//BufferInfo内定义了此数据块的大小
                            outBuf.get(data);//将Buffer内的数据取出到字节数组中
                            putPCMData(data, decodeBufferInfo.size);//自己定义的方法，供编码器所在的线程获取数据,下面会贴出代码
                        }

                        mediaDecode.releaseOutputBuffer(outputIndex, false);//此操作一定要做，不然MediaCodec用完所有的Buffer后 将不能向外输出数据

                        if ((decodeBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                            sawOutputEOS = true;
                        }

                    } else if (outputIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
                        decodeOutputBuffers = mediaDecode.getOutputBuffers();
                    }
                } else {
                    Thread.sleep(50);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (mediaDecode != null) {
                mediaDecode.release();
            }
            if (mediaExtractor != null) {
                mediaExtractor.release();
            }
        }
    }


    private class PCM {
        PCM(byte[] bufferBytes, int bufferSize) {
            this.bufferBytes = bufferBytes;
            this.bufferSize = bufferSize;
        }

        byte[] bufferBytes;
        int bufferSize;
    }
}
